package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@SuppressWarnings("all")
public class SearchServiceImpl implements SearchService{
    @Autowired
    private GoodsRepository goodRepository;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    //es高级api
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    /**
     * 商品搜索之上架
     * @param skuId
     */
    @Override
    public void upperGoods(Long skuId) {
        /**
         * 通过页面分析可知,搜索所需要的数据有:
         * Sku基本信息
         * Sku分类信息
         * Sku的品牌信息
         * Sku对应的平台属性
         * 以上数据全部来自service-product
         * 所以这里远程调用该模块
         * 最后把获取到的数据保存在es中,供搜索使用
         */
        //先new一个es的映射类用来整合数据,最后存储
        Goods goods = new Goods();

        //Sku基本信息=======================================
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);

        if(null != skuInfo){
            //封装skuInfo数据
            goods.setId(skuInfo.getId());
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            goods.setTitle(skuInfo.getSkuName());
            //因为详情页只能从首页跳转到详情页,也就是说,skuInfo还没进缓存,肯定是第一次查,价格是最新的
            // 所以,这里直接获取就可以了,不用单独调用查询价格的接口方法了
            goods.setPrice(skuInfo.getPrice().doubleValue());
            goods.setCreateTime(new Date());
        }

        //Sku分类信息==============================
        BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        if(null != categoryView){
            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());
        }

        //Sku的品牌信息=====================================
        BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
        if(null != trademark){
            goods.setTmId(trademark.getId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());
        }

        //Sku对应的平台属性======================================
        List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
        if(null !=attrList){
            List<SearchAttr> searchAttrList = attrList.stream().map(attr -> {
                SearchAttr searchAttr = new SearchAttr();

                searchAttr.setAttrId(attr.getId());
                searchAttr.setAttrName(attr.getAttrName());
                searchAttr.setAttrValue(attr.getAttrValueList().get(0).getValueName());
                return searchAttr;
            }).collect(Collectors.toList());
            goods.setAttrs(searchAttrList);
        }

        //数据已经封装完成,调用goodRepository继承了ElasticsearchRepository<Goods,Long>,这样更方便了es的操作
        this.goodRepository.save(goods);
    }

    /**
     * 商品搜索之下架
     * @param skuId
     */
    @Override
    public void lowerGoods(Long skuId) {
        goodRepository.deleteById(skuId);
    }

    /**
     * 更新商品的热度排名
     * @param skuId
     */
    @Override
    public void incrHotScore(Long skuId) {
        //定义key
        String hotKey="HotScore";
        Double hotScore = redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        //判断,如果加了10次在执行es的更新
        if(hotScore%10==0){
            Optional<Goods> optional = goodRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(Math.round(hotScore));
            goodRepository.save(goods);
        }

    }

    /**
     * 商品搜索
     * @param searchParam
     * @return
     * 1.编写dsl
     * 2.发送dsl
     * 3.处理结果
     * 问题:发送给谁
     */
    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        //构建DSL语句
        SearchRequest searchRequest=buidSearchDsl(searchParam);
        //发送请求
        SearchResponse searchResponse = null;

        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //处理返回结果
        SearchResponseVo searchResponseVo=this.parSearchResult(searchResponse);
        //设置每页记录数
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //设置当前页
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //计算总页数
        //总页数=(总记录数+每页条数-1)/每页条数   (这个思路代码简洁一些)
        long totalPage =(searchResponseVo.getTotal()+searchResponseVo.getPageSize()-1)/searchResponseVo.getPageSize();
        searchResponseVo.setTotalPages(totalPage);
        return searchResponseVo;
    }


    /**
     * //构建DSL语句
     * @param searchParam
     * @return
     */
    private SearchRequest buidSearchDsl(SearchParam searchParam) {
        SearchRequest searchRequest = new SearchRequest();
        //创建条件构造器 简单  相当于最外层的大括号
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //构建条件,第一个bool
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //封装关键字匹配条件
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
        }

        //封装分类
        if(searchParam.getCategory1Id()!=null){
            //创建过滤查询对象
            boolQuery.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }
        if(searchParam.getCategory2Id()!=null){
            //创建过滤查询对象
            boolQuery.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if(searchParam.getCategory3Id()!=null){
            //创建过滤查询对象
            boolQuery.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }

        //封装品牌  2:华为
        String trademark = searchParam.getTrademark();
        if(!StringUtils.isEmpty(trademark)){
            String[] split = trademark.split(":");
            //判断
            if(split !=null && split.length==2){
                boolQuery.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }

        //封装平台属性 ["23:4G:运行内存","23:4G:运行内存"]
        String[] props = searchParam.getProps();
        //判断
        if(props!=null && props.length>0){


            for (String prop : props) {
                //23:4G:运行内存
                //截取
                String[] split = prop.split(":");
                //判断
                if(split!=null && split.length==3){
                    //创建平台属性外层的bool
                    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                    //创建平台属性内存bool
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();

                    subBoolQuery.filter(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                    subBoolQuery.filter(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    //建立结构关系
                    boolQuery.filter(boolQueryBuilder.must(QueryBuilders.nestedQuery("attrs",subBoolQuery, ScoreMode.None)));
                }
            }
        }
        //将条件封装到构造器
        searchSourceBuilder.query(boolQuery);

        //分页条件  索引=(当前页-1)*每页记录数
        searchSourceBuilder.from((searchParam.getPageNo()-1)*searchParam.getPageSize());
        searchSourceBuilder.size(searchParam.getPageSize());
        //排序操作
        //数据内容:desc
        //1:hotScore 或者2:price
        //1表示热度,2表示价格
        if(!StringUtils.isEmpty(searchParam.getOrder())){
            //截取
            String[] split = searchParam.getOrder().split(":");
            //判断
            if(split!=null && split.length==2){
                //定义查询字段
                String filed="";
                //确认字段
                switch (split[0]){
                    case "1":
                        filed="hotScore";
                        break;
                    case "2":
                        filed="price";
                        break;
                }
                searchSourceBuilder.sort(filed,"asc".equalsIgnoreCase(split[1])? SortOrder.ASC:SortOrder.DESC);
            }
        }else {
            searchSourceBuilder.sort("hotScore",SortOrder.DESC);
        }

        //添加聚合--品牌信息
        //创建聚合对象
        TermsAggregationBuilder tmIdagg = AggregationBuilders.terms("tmIdagg").field("tmId");

        tmIdagg.subAggregation(AggregationBuilders.terms("tmNameagg").field("tmName"));
        tmIdagg.subAggregation(AggregationBuilders.terms("tmLogoUrlagg").field("tmLogoUrl"));

        //聚合--平台属性
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrsagg","attrs").subAggregation(
                AggregationBuilders.terms("attrIdagg").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameagg").field("attrs.attrName"))
                .subAggregation(AggregationBuilders.terms("attrValueagg").field("attrs.attrValue"))
        ));
        searchSourceBuilder.aggregation(tmIdagg);

        //高亮
        //创建高亮对象
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        //指定高亮的前缀
        highlightBuilder.preTags(new String[]{"<span style='color:red'>"});
        //指定高亮的后缀
        highlightBuilder.postTags(new String[]{"</span>"});

        searchSourceBuilder.highlighter(highlightBuilder);

        //处理返回的结果--指定返回的字段
        searchSourceBuilder.fetchSource(new String[]{"id","title","defaultImg","price"},null);

        //构造器条件到请求对象
        System.out.println("DSL:\t"+searchSourceBuilder.toString());
        searchRequest.source(searchSourceBuilder);
        return searchRequest;
    }

    /**
     * //处理返回结果
     * @param searchResponse
     * @return
     */
    private SearchResponseVo parSearchResult(SearchResponse searchResponse) {
        //创建返回值结果对象
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        //获取的所有聚合信息包括-品牌和平台属性
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        //封装品牌信息
        ParsedLongTerms tmIdagg = (ParsedLongTerms) aggregationMap.get("tmIdagg");
        List<? extends Terms.Bucket> buckets = tmIdagg.getBuckets();
        //判断
        if(!CollectionUtils.isEmpty(buckets)){
            List<SearchResponseTmVo> searchResponseTmVoList = buckets.stream().map(bucket -> {
                //创建品牌对象
                SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
                //bucket一个品牌所有信息
                long tmId = ((Terms.Bucket) bucket).getKeyAsNumber().longValue();
                searchResponseTmVo.setTmId(tmId);
                //获取名称和路径的聚合信息子查询对象
                Map<String, Aggregation> subAggregationMap = ((Terms.Bucket) bucket).getAggregations().asMap();
                //获取名称tmName
                ParsedStringTerms tmNameagg = (ParsedStringTerms) subAggregationMap.get("tmNameagg");
                String tmName = tmNameagg.getBuckets().get(0).getKeyAsString();
                searchResponseTmVo.setTmName(tmName);

                //获取LogoUrl
                ParsedStringTerms tmLogoUrlagg = (ParsedStringTerms) subAggregationMap.get("tmLogoUrlagg");
                String tmLogoUrl = tmLogoUrlagg.getBuckets().get(0).getKeyAsString();
                searchResponseTmVo.setTmLogoUrl(tmLogoUrl);

                return searchResponseTmVo;
            }).collect(Collectors.toList());
            //封装品牌信息
            searchResponseVo.setTrademarkList(searchResponseTmVoList);
        }


        //封装平台属性
        ParsedNested attrsagg = (ParsedNested) aggregationMap.get("attrsagg");
        ParsedLongTerms attrIdagg = (ParsedLongTerms) attrsagg.getAggregations().asMap().get("attrIdagg");
        //封装平台数据
        List<? extends Terms.Bucket> attrsBuckets = attrIdagg.getBuckets();
        //判断
        if(!CollectionUtils.isEmpty(attrsBuckets)){
            List<SearchResponseAttrVo> attrVoList = attrsBuckets.stream().map(bucket -> {
                //创建平台属性对象
                SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
                //获取平台属性Id
                long attrId = ((Terms.Bucket) bucket).getKeyAsNumber().longValue();
                searchResponseAttrVo.setAttrId(attrId);
                //获取子聚合--平台属性名和平台属性值
                Map<String, Aggregation> attrSubAggretion = bucket.getAggregations().asMap();
                //获取属性名
                ParsedStringTerms attrNameagg = (ParsedStringTerms) attrSubAggretion.get("attrNameagg");
                String attrName = attrNameagg.getBuckets().get(0).getKeyAsString();
                searchResponseAttrVo.setAttrName(attrName);
                //获取属性值
                ParsedStringTerms attrValueagg = (ParsedStringTerms) attrSubAggretion.get("attrValueagg");
                List<? extends Terms.Bucket> attrValueaggBuckets = attrValueagg.getBuckets();
                //判断
                if (!CollectionUtils.isEmpty(attrValueaggBuckets)) {
                    List<String> attrValueList = attrValueaggBuckets.stream().map(attrValue -> {
                        return ((Terms.Bucket) attrValue).getKeyAsString();
                    }).collect(Collectors.toList());
                    //设置平台属性值集合
                    searchResponseAttrVo.setAttrValueList(attrValueList);
                }
                return searchResponseAttrVo;
            }).collect(Collectors.toList());

            searchResponseVo.setAttrsList(attrVoList);
        }


        //封装商品信息
        SearchHits searchHits = searchResponse.getHits();
        //获取数据
        SearchHit[] hits = searchHits.getHits();
        //判断
        if(hits!=null && hits.length >0){
            List<Goods> goodsList = Arrays.stream(hits).map(hit -> {
                Goods goods = JSON.parseObject(hit.getSourceAsString(), Goods.class);
                //处理高亮
                if (hit.getHighlightFields().get("title") != null) {
                    String title = hit.getHighlightFields().get("title").getFragments()[0].toString();
                    goods.setTitle(title);
                }
                return goods;
            }).collect(Collectors.toList());
            //设置商品数据集合
            searchResponseVo.setGoodsList(goodsList);
        }


        //获取总记录数
        long total = searchHits.getTotalHits().value;
        searchResponseVo.setTotal(total);

        return searchResponseVo;
    }
}













